package com.xuecheng.orders.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.xuecheng.base.constant.*;
import com.xuecheng.base.exception.CommonError;
import com.xuecheng.base.exception.XCPlusBaseException;
import com.xuecheng.base.util.QRCodeUtil;
import com.xuecheng.base.util.SecurityUtil;
import com.xuecheng.orders.config.AliPayProperties;
import com.xuecheng.orders.exception.OrderPaidException;
import com.xuecheng.orders.exception.OrdersError;
import com.xuecheng.orders.model.dto.AddOrderDTO;
import com.xuecheng.orders.model.dto.PayRecordDTO;
import com.xuecheng.orders.model.dto.PayStatusDTO;
import com.xuecheng.orders.model.po.XcOrders;
import com.xuecheng.orders.model.po.XcOrdersGoods;
import com.xuecheng.orders.model.po.XcPayRecord;
import com.xuecheng.orders.service.IOrderService;
import com.xuecheng.orders.service.IXcOrdersGoodsService;
import com.xuecheng.orders.service.IXcOrdersService;
import com.xuecheng.orders.service.IXcPayRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * @author 勾新杰
 * @version 1.0
 * @description:
 * @date 2025/3/1 18:08
 */
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements IOrderService {

    private final IXcOrdersService xcOrdersService;

    private final IXcOrdersGoodsService xcOrdersGoodsService;

    private final IXcPayRecordService xcPayRecordService;

    private final AliPayProperties aliPayProperties;

    private final AlipayClient alipayClient;

    private final RabbitTemplate rabbitTemplate;

    private static final Integer DEFAULT_WIDTH = 300;

    private static final Integer DEFAULT_HEIGHT = 300;

    private static final Integer DEFAULT_EXPIRE_MILLIS_SECOND = 900000;

    /**
     * 生成订单和二维码
     *
     * @param addOrderDTO 订单信息
     * @return 支付记录和二维码（此时还未支付）
     */
    @Override
    @Transactional
    public PayRecordDTO createOrder(AddOrderDTO addOrderDTO) {
        // 1. 根据用户id和选课id查询该用户是否有该课的订单
        SecurityUtil.XcUser user = SecurityUtil.getUser();
        String outBusinessId = addOrderDTO.getOutBusinessId(); // 外部业务号，这里就是选课id
        XcOrders xcOrders = xcOrdersService.lambdaQuery()
                .eq(XcOrders::getOutBusinessId, outBusinessId)
                .eq(XcOrders::getUserId, Objects.requireNonNull(user).getId())
                .one();
        // 2. 如果有订单，并且已支付则抛出异常，其它情况都可以继续支付
        if (xcOrders != null && OrderStatusConstant.PAID.equals(xcOrders.getStatus()))
            OrderPaidException.cast();
        // 3. 如果没有订单，则生成订单和订单详情
        Snowflake snowflake = IdUtil.getSnowflake();
        if (xcOrders == null) {
            // 3.1 插入订单记录
            long id = snowflake.nextId();
            xcOrders = new XcOrders()
                    .setId(id)
                    .setTotalPrice(addOrderDTO.getTotalPrice())
                    .setCreateDate(LocalDateTime.now())
                    .setStatus(OrderStatusConstant.UNPAID)
                    .setUserId(user.getId())
                    .setOrderType(addOrderDTO.getOrderType())
                    .setOrderName(addOrderDTO.getOrderName())
                    .setOrderDescrip(addOrderDTO.getOrderDescrip())
                    .setOrderDetail(addOrderDTO.getOrderDetail())
                    .setOutBusinessId(outBusinessId);
            boolean save = xcOrdersService.save(xcOrders);
            if (!save) XCPlusBaseException.cast(CommonError.FAILED_INSERT_TO_DATABASE);
            // 3.2 插入订单详情记录
            List<XcOrdersGoods> ordersGoodsList = JSON.parseArray(addOrderDTO.getOrderDetail(), XcOrdersGoods.class);
            ordersGoodsList.forEach(xcOrdersGoods -> xcOrdersGoods.setOrderId(id));
            save = xcOrdersGoodsService.saveBatch(ordersGoodsList);
            if (!save) XCPlusBaseException.cast(CommonError.FAILED_INSERT_TO_DATABASE);
        }
        // 4. 生成支付记录，支付流水
        long payId = snowflake.nextId();
        XcPayRecord xcPayRecord = new XcPayRecord()
                .setPayNo(payId)
                .setOrderId(xcOrders.getId())
                .setOrderName(xcOrders.getOrderName())
                .setTotalPrice(xcOrders.getTotalPrice())
                .setCurrency(OrderCurrencyConstant.CNY)
                .setCreateDate(LocalDateTime.now())
                .setStatus(OrderPayRecordStatusConstant.UNPAID)
                .setUserId(xcOrders.getUserId());
        boolean save = xcPayRecordService.save(xcPayRecord);
        if (!save) XCPlusBaseException.cast(CommonError.FAILED_INSERT_TO_DATABASE);
        // 5. 生成二维码
        String qrCodeUrl = String.format(aliPayProperties.getQrCodeUrl(), xcPayRecord.getPayNo(), System.currentTimeMillis() + DEFAULT_EXPIRE_MILLIS_SECOND); // 五分钟后二维码失效
        String qrCode = QRCodeUtil.createQRCode(qrCodeUrl, DEFAULT_WIDTH, DEFAULT_HEIGHT);
        // 6. 发送延迟消息，15分钟后还未支付，则关闭订单
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_DELAY_DIRECT, RabbitMQConstant.ROUTING_KEY_DELAY_PAY_RECORD, String.valueOf(payId),
                message -> {
                    message.getMessageProperties().setDelay(DEFAULT_EXPIRE_MILLIS_SECOND);
                    return message;
                });
        // 7. 构建返回对象返回
        PayRecordDTO payRecordDTO = BeanUtil.copyProperties(xcPayRecord, PayRecordDTO.class);
        payRecordDTO.setPayNo(String.valueOf(xcPayRecord.getPayNo()));
        payRecordDTO.setOrderId(String.valueOf(xcPayRecord.getOrderId()));
        payRecordDTO.setQrcode(qrCode);
        return payRecordDTO;
    }

    /**
     * 根据支付交易号查询支付记录
     *
     * @param payNo 支付交易号
     * @return 支付记录
     */
    @Override
    public XcPayRecord getByPayNo(String payNo) {
        return xcPayRecordService.lambdaQuery()
                .eq(XcPayRecord::getPayNo, payNo)
                .one();
    }

    /**
     * 查询支付结果
     *
     * @param payNo 支付交易号
     * @return 支付记录
     */
    @Override
    @Transactional
    public PayRecordDTO queryPayResult(String payNo) {
        // 1. 先试着从数据库的支付记录表中去查
        XcPayRecord payRecord = getByPayNo(payNo);
        // 2. 如果查不到，应该提示该交易不存在
        if (payRecord == null) throw new XCPlusBaseException(OrdersError.HAVE_NO_THIS_TRADE_NO.getErrMessage());
        // 3. 如果状态为已支付，则直接返回
        if (OrderPayRecordStatusConstant.PAID.equals(payRecord.getStatus())) {
            PayRecordDTO payRecordDTO = BeanUtil.copyProperties(payRecord, PayRecordDTO.class);
            payRecordDTO.setPayNo(String.valueOf(payRecord.getPayNo()));
            payRecordDTO.setOrderId(String.valueOf(payRecord.getOrderId()));
            return payRecordDTO;
        }
        // 4. 如果是未支付，则调用支付宝查询接口
        PayStatusDTO payStatusDTO = queryPayResultFromAliPay(payNo);
        // 4.1 校验返回结果是否合法
        if (!payStatusDTO.getApp_id().equals(aliPayProperties.getAppid()))
            throw new XCPlusBaseException(OrdersError.CHECK_PAY_RESULT_FAILED.getErrMessage());
        // 4.2 校验支付状态是否为成功
        if (!OrderAlipayTradeStatusConstant.TRADE_SUCCESS.equals(payStatusDTO.getTrade_status())
                && !OrderAlipayTradeStatusConstant.TRADE_FINISHED.equals(payStatusDTO.getTrade_status()))
            throw new XCPlusBaseException(OrdersError.TRADE_NOT_PAY_SUCCESS.getErrMessage());
        // 4.3 校验支付金额是否一致
        if (payRecord.getTotalPrice() * 100 != Float.parseFloat(payStatusDTO.getTotal_amount()) * 100)
            throw new XCPlusBaseException(OrdersError.PAY_AMOUNT_NOT_EQUAL.getErrMessage());
        // 5. 如果查询到，则更新支付记录表和订单表，并返回
        paySuccess(payNo, payStatusDTO.getTrade_no(), OrderPayChannelConstant.ALIPAY);
        // 5.3 重新查，返回支付记录
        payRecord = getByPayNo(payNo);
        PayRecordDTO payRecordDTO = BeanUtil.copyProperties(payRecord, PayRecordDTO.class);
        payRecordDTO.setPayNo(String.valueOf(payRecord.getPayNo()));
        payRecordDTO.setOrderId(String.valueOf(payRecord.getOrderId()));
        return payRecordDTO;
    }

    /**
     * 调用支付宝查询接口
     *
     * @param payNo 支付交易号（本系统）
     * @return 支付结果
     */
    private PayStatusDTO queryPayResultFromAliPay(String payNo) {
        try {
            // 1. 准备请求对象
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
            AlipayTradeQueryModel model = new AlipayTradeQueryModel();
            // 2. 设置查询参数
            model.setOutTradeNo(payNo);
            request.setBizModel(model);
            AlipayTradeQueryResponse response = alipayClient.execute(request);
            // 3. 响应成功
            if (response.isSuccess()) {
                return new PayStatusDTO()
                        .setApp_id(aliPayProperties.getAppid())
                        .setTrade_no(response.getTradeNo())
                        .setOut_trade_no(response.getOutTradeNo())
                        .setTotal_amount(response.getTotalAmount())
                        .setTrade_status(response.getTradeStatus());
            } else {
                throw new XCPlusBaseException(OrdersError.FAILED_QUERY_PAY_RESULT.getErrMessage());
            }
        } catch (AlipayApiException e) {
            throw new XCPlusBaseException(OrdersError.FAILED_QUERY_PAY_RESULT.getErrMessage());
        }
    }

    /**
     * 支付成功后更新支付记录
     *
     * @param payNo      支付交易号
     * @param outTradeNo 外部支付交易号
     * @param payChannel 支付渠道
     */
    @Override
    public void paySuccess(String payNo, String outTradeNo, String payChannel) {
        // 1. 更新支付记录表
        xcPayRecordService.lambdaUpdate()
                .eq(XcPayRecord::getPayNo, payNo)
                .set(XcPayRecord::getOutPayNo, outTradeNo)
                .set(XcPayRecord::getOutPayChannel, payChannel)
                .set(XcPayRecord::getPaySuccessTime, LocalDateTime.now())
                .set(XcPayRecord::getStatus, OrderPayRecordStatusConstant.PAID)
                .update();
        // 2. 获取该支付流水对应的订单id
        XcPayRecord payRecord = getByPayNo(payNo);
        // 3. 更新订单表
        xcOrdersService.lambdaUpdate()
                .eq(XcOrders::getId, payRecord.getOrderId())
                .set(XcOrders::getStatus, OrderStatusConstant.PAID)
                .update();
    }

    /**
     * 根据订单号查询订单
     *
     * @param orderId 订单号
     * @return 订单
     */
    @Override
    public XcOrders getOrderById(Long orderId) {
        return xcOrdersService.lambdaQuery()
                .eq(XcOrders::getId, orderId)
                .one();
    }

    /**
     * 关闭交易
     *
     * @param payNo 支付流水号
     */
    @Override
    public void closeTrade(String payNo) {
        xcPayRecordService.lambdaUpdate()
                .eq(XcPayRecord::getPayNo, payNo)
                .set(XcPayRecord::getStatus, OrderPayRecordStatusConstant.CLOSED)
                .update();
    }
}
